columnview: Interactive column reordering
authorMatthias Clasen <mclasen@redhat.com>
Fri, 20 Dec 2019 22:29:35 +0000 (17:29 -0500)
committerMatthias Clasen <mclasen@redhat.com>
Wed, 3 Jun 2020 17:31:39 +0000 (13:31 -0400)
Allow rearranging columns by dragging, in the same
way the treeview does.

We add the "dnd" style class to the header while
it is dragged, and we move the header of the dragged
column to the end of its parents children, so that
it gets drawn on top.

gtk/gtkcolumnview.c

index 414ca0d83d27ca9f1660b857b9dce259c4cd74bc..73704328b73c94b5d9ac5523adb3a3e20873435a 100644 (file)
@@ -38,6 +38,7 @@
 #include "gtkadjustment.h"
 #include "gtkgesturedrag.h"
 #include "gtkeventcontrollermotion.h"
+#include "gtkdragsource.h"
 
 /**
  * SECTION:gtkcolumnview
@@ -73,8 +74,11 @@ struct _GtkColumnView
   gboolean reorderable;
 
   gboolean in_column_resize;
+  gboolean in_column_reorder;
   int drag_pos;
   int drag_x;
+  int drag_offset;
+  int drag_column_x;
 };
 
 struct _GtkColumnViewClass
@@ -224,6 +228,9 @@ gtk_column_view_allocate_columns (GtkColumnView *self,
             col_size = sizes[i].natural_size;
 
           gtk_column_view_column_allocate (column, x, col_size);
+          if (self->in_column_reorder && i == self->drag_pos)
+            gtk_column_view_column_set_header_position (column, self->drag_x);
+
           x += col_size;
         }
 
@@ -602,6 +609,23 @@ gtk_column_view_in_resize_rect (GtkColumnView       *self,
   return graphene_rect_contains_point (&rect, &(graphene_point_t) { x, y});
 }
 
+static gboolean
+gtk_column_view_in_header (GtkColumnView       *self,
+                           GtkColumnViewColumn *column,
+                           double               x,
+                           double               y)
+{
+  GtkWidget *header;
+  graphene_rect_t rect;
+
+  header = gtk_column_view_column_get_header (column);
+
+  if (!gtk_widget_compute_bounds (header, self->header, &rect))
+    return FALSE;
+
+  return graphene_rect_contains_point (&rect, &(graphene_point_t) { x, y});
+}
+
 static void
 header_drag_begin (GtkGestureDrag *gesture,
                    double          start_x,
@@ -610,6 +634,8 @@ header_drag_begin (GtkGestureDrag *gesture,
 {
   int i, n;
 
+  self->drag_pos = -1;
+
   n = g_list_model_get_n_items (G_LIST_MODEL (self->columns));
   for (i = 0; !self->in_column_resize && i < n; i++)
     {
@@ -631,6 +657,7 @@ header_drag_begin (GtkGestureDrag *gesture,
 
           gtk_column_view_column_get_allocation (column, NULL, &size);
           gtk_column_view_column_set_fixed_width (column, size);
+
           self->drag_pos = i;
           self->drag_x = start_x - size;
           self->in_column_resize = TRUE;
@@ -639,6 +666,20 @@ header_drag_begin (GtkGestureDrag *gesture,
           break;
         }
 
+      if (gtk_column_view_get_reorderable (self) &&
+          gtk_column_view_in_header (self, column, start_x, start_y))
+        {
+          int pos;
+
+          gtk_column_view_column_get_allocation (column, &pos, NULL);
+
+          self->drag_pos = i;
+          self->drag_offset = start_x - pos;
+
+          g_object_unref (column);
+          break;
+        }
+
       g_object_unref (column);
     }
 }
@@ -649,7 +690,49 @@ header_drag_end (GtkGestureDrag *gesture,
                  double          offset_y,
                  GtkColumnView  *self)
 {
-  self->in_column_resize = FALSE;
+  double start_x, x;
+
+  gtk_gesture_drag_get_start_point (gesture, &start_x, NULL);
+  x = start_x + offset_x;
+
+  if (self->in_column_resize)
+    {
+      self->in_column_resize = FALSE;
+    }
+  else if (self->in_column_reorder)
+    {
+      GtkColumnViewColumn *column;
+      GtkWidget *header;
+      int i;
+
+      column = g_list_model_get_item (G_LIST_MODEL (self->columns), self->drag_pos);
+      header = gtk_column_view_column_get_header (column);
+      gtk_style_context_remove_class (gtk_widget_get_style_context (header), "dnd");
+
+      for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->columns)); i++)
+        {
+          GtkColumnViewColumn *col = g_list_model_get_item (G_LIST_MODEL (self->columns), i);
+
+          if (gtk_column_view_column_get_visible (col))
+            {
+              int pos, size;
+
+              gtk_column_view_column_get_allocation (col, &pos, &size);
+              if (pos <= x && x <= pos + size)
+                {
+                  gtk_column_view_insert_column (self, i, column);
+                  g_object_unref (col);
+                  break;
+                }
+            }
+
+          g_object_unref (col);
+        }
+
+      g_object_unref (column);
+
+      self->in_column_reorder = FALSE;
+    }
 }
 
 static void
@@ -663,6 +746,25 @@ update_column_resize (GtkColumnView *self,
   g_object_unref (column);
 }
 
+static void
+update_column_reorder (GtkColumnView *self,
+                       double         x)
+{
+  GtkColumnViewColumn *column;
+  int width;
+  int size;
+
+  column = g_list_model_get_item (G_LIST_MODEL (self->columns), self->drag_pos);
+  width = gtk_widget_get_allocated_width (GTK_WIDGET (self->header));
+  gtk_column_view_column_get_allocation (column, NULL, &size);
+
+  self->drag_x = CLAMP (x - self->drag_offset, 0, width - size);
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+  gtk_column_view_column_queue_resize (column);
+  g_object_unref (column);
+}
+
 static void
 header_drag_update (GtkGestureDrag *gesture,
                     double          offset_x,
@@ -671,11 +773,37 @@ header_drag_update (GtkGestureDrag *gesture,
 {
   double start_x, x;
 
+  if (self->drag_pos == -1)
+    return;
+
+
+  if (!self->in_column_resize && !self->in_column_reorder)
+    {
+      if (gtk_drag_check_threshold (GTK_WIDGET (self), 0, 0, offset_x, 0))
+        {
+          GtkColumnViewColumn *column;
+          GtkWidget *header;
+
+          column = g_list_model_get_item (G_LIST_MODEL (self->columns), self->drag_pos);
+          header = gtk_column_view_column_get_header (column);
+
+          gtk_widget_insert_after (header, self->header, gtk_widget_get_last_child (self->header));
+          gtk_style_context_add_class (gtk_widget_get_style_context (header), "dnd");
+
+          gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+          self->in_column_reorder = TRUE;
+
+          g_object_unref (column);
+        }
+    }
+
   gtk_gesture_drag_get_start_point (gesture, &start_x, NULL);
   x = start_x + offset_x;
 
   if (self->in_column_resize)
     update_column_resize (self, x);
+  else if (self->in_column_reorder)
+    update_column_reorder (self, x);
 }
 
 static void